#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include<fstream>
#include <math.h>
using namespace std;
#include "image.h"
//**********************************************
//Function name:ImageType default constructor
//Return Type:	void
//Purpose:	Initializes all values for the
//			ImageType class to their null
//			values.
//*********************************************
ImageType::ImageType()
{
	N = 0;
	M = 0;
	Q = 0;

	pixelValue = NULL;
}

//**********************************************
//Function name:ImageType constructor
//Purpose:	Constructor for the Image class.
//			Initializes all variables in 
//			the class and allocates an 
//			appropriate amount of memory
//			to be used by the class for
//			image storage.
//*********************************************
ImageType::ImageType(int tmpN, int tmpM, int tmpQ)
{
	int i, j;

	N = tmpN;
	M = tmpM;
	Q = tmpQ;

	pixelValue = new int* [N];
	for(i=0; i<N; i++)
	{
		pixelValue[i] = new int[M];
		for(j=0; j<M; j++)
		{
			pixelValue[i][j] = 0;
		}
	}
}

//**********************************************
//Function name:ImageType copy constructor
//Purpose:	Copies all the values in the 
//			passed image file. Allocates
//			an appropriate amount of memory
//			for the image itself to be
//			stored.
//*********************************************
ImageType::ImageType(const ImageType &copyImage)
{
	M = copyImage.M;
	N = copyImage.N;
	Q = copyImage.Q;
	pixelValue = new int *[N];
	//***************************************************************
	//The nested for loop allocates rows to the pixelVal array and
	//fills the individual elements of the array with the copied
	//pixel values from the passed image.
	//***************************************************************
	for(int rowCount = 0; rowCount < N; rowCount++)
	{
		pixelValue[rowCount] = new int[M];
		for(int colCount = 0; colCount < M; colCount++)
		{
			pixelValue[rowCount][colCount] = copyImage.pixelValue[rowCount][colCount];
		}
	}
}

//*************************************************
//Function name:Image Deconstructor
//Purpose:	Deallocate all dynamically allocated
//			memory. Deallocates the 2D array used
//			by the Image class.
//**************************************************
ImageType::~ImageType()
{
	for (int colCount=0; colCount < (N-1); colCount++)
    {
		delete [] pixelValue [colCount];
    }
	delete [] pixelValue;

}

//**********************************************
//Function name:getImageInfo
//Return Type:	Void
//Purpose:	Return the number of columns, 
//			rows, and pixel count in the
//			image array. Uses pass-by-
//			reference to assign those 
//			values to the variables passed.	
//*********************************************	
void ImageType::getImageInfo(int& rows, int& cols, int& levels)
{
    rows = N;
    cols = M;
    levels = Q;
} 
//**********************************************
//Function name:seImageInfo
//Return Type:	Void
//Purpose:	Sets the information of the image's
//			rows, colums, and max gray level.	
//*********************************************
void ImageType::setImageInfo(int rows, int cols, int levels)
{
    N= rows;
    M= cols;
    Q= levels;
} 
//**********************************************
//Function name:setPixelVal
//Return Type:	Void
//Purpose:	Sets the value of the pixel at
//			the specified coordinates to
//			the desired value. Throws an 
//			OUT-OF-BOUNDS exception if the
//			coordinates fall outside the image.
//*********************************************
void ImageType::setPixelVal(const int i,const int j, int pixelV)
{
	if(j > M || i > N || i < 0 || j < 0)
	{
		//************************************
		//If the coordinates are outside the
		//image, this error will occur, and 
		//the function will exit.
		//************************************
		cout << "ERROR: OUT-OF-BOUNDS";
	}
	else
		pixelValue[i][j] = pixelV;
}
//**********************************************
//Function name:getPixelVal
//Return Type:	Void
//Purpose:	Return the value of the pixel
//			at the specified coordinates.
//			Throws an OUT-OF-BOUNDS
//			exception if the coordinates
//			fall outside the image.	
//********************************************
int ImageType::getPixelVal(int i,int j) const
{
	int pixelV;
	if(j > M || i > N || i < 0 || j < 0)
	{
		//************************************
		//If the coordinates are outside the
		//image, this error will occur, and 
		//the function will exit.
		//************************************
		cout << "ERROR: OUT-OF-BOUNDS";
	}
	else
	{
		pixelV = pixelValue[i][j];
		return pixelV;
	}
}

void readImageHeader(char fname[], int& N, int& M, int& Q, bool& type)
{
    int i, j;
    unsigned char *charImage;
    char header [100], *ptr;
    ifstream ifp;

    ifp.open(fname, ios::in | ios::binary);

    if (!ifp) 
    {
    cout << "Can't read image: " << fname << endl;
    exit(1);
    }

 // read header

    type = false; // PGM

    ifp.getline(header,100,'\n');
    if ( (header[0] == 80) &&  /* 'P' */
         (header[1]== 53) ) 
        {  /* '5' */
            type = false;
        }
    else if ( (header[0] == 80) &&  /* 'P' */
              (header[1] == 54) ) 
            {
                /* '6' */
                type = true;
            } 
    else 
    {
        cout << "Image " << fname << " is not PGM or PPM" << endl;
        exit(1);
    }

    ifp.getline(header,100,'\n');
    while(header[0]=='#')
        ifp.getline(header,100,'\n');

    M=strtol(header,&ptr,0);
    N=atoi(ptr);

    ifp.getline(header,100,'\n');

    Q=strtol(header,&ptr,0);

    ifp.close();

}

void readImage(char fname[], ImageType& image)
{
    int i, j;
    int N, M, Q;
    unsigned char *charImage;
    char header [100], *ptr;
    ifstream ifp;

    ifp.open(fname, ios::in | ios::binary);

    if (!ifp) 
    {
        cout << "Can't read image: " << fname << endl;
        exit(1);
    }

 // read header

    ifp.getline(header,100,'\n');
    if ( (header[0]!=80) ||    /* 'P' */
         (header[1]!=53) ) 
    {   /* '5' */
            cout << "Image " << fname << " is not PGM" << endl;
            exit(1);
    }

    ifp.getline(header,100,'\n');
    while(header[0]=='#')
        ifp.getline(header,100,'\n');

    M=strtol(header,&ptr,0);
    N=atoi(ptr);

    ifp.getline(header,100,'\n');
    Q=strtol(header,&ptr,0);

    charImage = (unsigned char *) new unsigned char [M*N];

    ifp.read( reinterpret_cast<char *>(charImage), (M*N)*sizeof(unsigned char));

    if (ifp.fail()) 
    {
        cout << "Image " << fname << " has wrong size" << endl;
        exit(1);
    }

    ifp.close();

 //
 // Convert the unsigned characters to integers
 //

    int val;

    for(i=0; i<N; i++)
    {
        for(j=0; j<M; j++) 
        {
            val = (int)charImage[i*M+j];
            image.setPixelVal(i, j, val);     
        }
    }

    delete [] charImage;

}

void writeImage(char fname[], ImageType& image)
{
    int i, j;
    int N, M, Q;
    unsigned char *charImage;
    ofstream ofp;
    image.getImageInfo(N, M, Q);
    charImage = (unsigned char *) new unsigned char [M*N];

 // convert the integer values to unsigned char

    int pixelV;
    for(i=0; i<N; i++)
    {
        for(j=0; j<M; j++) 
        {
            pixelV = image.getPixelVal(i, j);
            charImage[i*M+j]=(unsigned char)pixelV;
        }
    }
    ofp.open(fname, ios::out | ios::binary);

    if (!ofp) 
    {
        cout << "Can't open file: " << fname << endl;
        exit(1);
    }

    ofp << "P5" << endl;
    ofp << M << " " << N << endl;
    ofp << Q << endl;
    ofp.write( reinterpret_cast<char *>(charImage), (M*N)*sizeof(unsigned char));
    if (ofp.fail()) 
    {
        cout << "Can't write image " << fname << endl;
        exit(0);
    }

    ofp.close();
    delete [] charImage;
}
//**********************************************
//Function name:getSubImage
//Return Type:	Void
//Purpose:	Crops a rectangular area within
//		the image, as specified by the
//		coordinates of the upper left
//		corner of the area to be cropped
//		and the coordinates of the 
//		lower right corner of the
//		area to be cropped. An OUT-OF-
//		BOUNDS exception is thrown if
//		the coordinates falls outside
//		the range of the image.
//*********************************************
void ImageType::getSubImage(int ULrows,int ULcols,int LRrows,int LRcols,ImageType &oldImage)
{
	if(ULcols > M || ULrows > N || LRcols < ULcols || LRrows < ULrows)
	{
		cout << "ERROR: OUT-OF-BOUNDS" << endl;
	}
	else
	{
		ImageType tempImage((LRrows - ULrows), (LRcols-ULcols), Q);
		int tempRCount = 0, tempCCount = 0, pValue;
		//*******************************************************
		//This double nested for loop sorts through the old image
		//and finds the coordinates of the subimage and copies
		//that to the temporary image array.
		//*******************************************************
		for(int subCount = ULrows; subCount < LRrows; subCount++)
		{
			tempCCount = 0;
			for(int subColCount = ULcols; subColCount < (LRcols - 1); subColCount++)
			{
				pValue = oldImage.getPixelVal(subCount, subColCount);
				tempImage.setPixelVal(tempRCount, tempCCount, pValue);
				tempCCount++;
			}
			tempRCount++;
		}
		int N, M, Q;
		oldImage = tempImage;
	}
}

//**********************************************
//Function name:meanGray
//Return Type:	int
//Purpose:	Sums and calculates the mean 
//		gray level value of the image.
//*********************************************
int ImageType::meanGray()
{
	int mean;
	//*************************************************************
	//This double nested for loop sums all of the pixel values
	//in the image into a single integer value.
	//*************************************************************
	for(int rowGray = 0; rowGray < N; rowGray++)
	{
		for(int colGray = 0; colGray < M; colGray++)
		{
			mean = mean + pixelValue[rowGray][colGray];
		}
	}
	//**************************************************************
	//Calculating the mean by dividing the summed pixel values by
	//the number of pixels, calculated by multiplying the number
	//of columns by the number of rows before dividing the mean
	//by that number.
	//**************************************************************
	mean = int(floor(mean / (N * M)));
	return mean;
}
//**********************************************
//Function name:enlargeImage
//Return Type:	ImageType
//Purpose:	Enlarges the image by a scaling 
//			factor and returns the scaled
//			image.
//*********************************************
void ImageType::enlargeImage(int s, ImageType& oldImage)
{
    int pixelV;
	int MM=int(ceil(M*s));
	int NN=int(ceil(N*s));
	ImageType imageScaled(NN, MM, Q);
	
	// enlarge image 
	for(int i=0; i<N; i++)
    {
		for(int j=0; j<M; j++) 
        {
			pixelV = oldImage.getPixelVal(i, j);
			imageScaled.setPixelVal(int(floor(i*s)), int(floor(j*s)), pixelV);

		}
    }
    oldImage = imageScaled;
}
//**********************************************
//Function name:shrinkImage
//Return Type:	void
//Purpose:	Shrinks the image by a scaling 
//			factor s. The algorithm is the 
//			exact same as the enlarge image
//			function, except it divides by
//			s instead of multiplying by s.
//*********************************************
void ImageType::shrinkImage(int s, ImageType& oldImage)
{
    int pixelV;
	int MM=int(ceil(M/s));
	int NN=int(ceil(N/s));
	ImageType imageScaled(NN, MM, Q);
	
	// enlarge image 
	for(int i=0; i<N; i++)
    {
		for(int j=0; j<M; j++) 
        {
			pixelV = oldImage.getPixelVal(i, j);
			imageScaled.setPixelVal(int(floor(i/s)), int(floor(j/s)), pixelV);

		}
    }
    oldImage = imageScaled;
}
//**********************************************
//Function name:operator=
//Return Type:	void
//Purpose:	Sets the image equal to the passed
//			image.
//*********************************************
ImageType &ImageType::operator = (const ImageType & image)
{
	int value;
	//********************************************
	//Loop deletes the image before reallocating
	//the memory required for the new image size.
	//********************************************
	for (int colCount=0; colCount < N; colCount++)
    {
		delete [] pixelValue [colCount];
    }
	delete [] pixelValue; 
	N = image.N;
	M = image.M;
	//*********************************************
	//This for loop reallocates new memory equal to
	//the size of the passed image and then sets
	//the value of each pixel to be that of the
	//passed image.
	//*********************************************
	pixelValue = new int* [N];
	for(int i=0; i<N; i++)
	{
		pixelValue[i] = new int[M];
		for(int j=0; j<M; j++)
			{
				value = image.getPixelVal(i, j);
				setPixelVal(i, j, value);
			}
	} 	
	return *this; 
}
//**********************************************
//Function name:operator+
//Return Type:	ImageType &
//Purpose:	Calculates the sum of each pixel 
//			for the two images and changes the
//			left side of the operator to the new
//			summed image.
//*********************************************
ImageType &ImageType::operator+(ImageType &rightSide)
{
	ImageType sum(N, M, Q);
	int val1 = 0, val2 = 0, val3 = 0;
	for(int sumRow = 0; sumRow < N; sumRow++)
	{
		for(int sumCol = 0; sumCol < M; sumCol++)
		{
			val1 = rightSide.getPixelVal(sumRow, sumCol);
			val2 = getPixelVal(sumRow, sumCol);
			val3 = int(val1/2) + int(val2/2);
			sum.setPixelVal(sumRow, sumCol, val3);
		}
	}
	*this = sum;
	return *this;
}
//**********************************************
//Function name:operator-
//Return Type:	ImageType &
//Purpose:	Calculates the difference of two
//			images and changes the image in the
//			left side of the operator to the 
//			new difference.
//*********************************************
ImageType &ImageType::operator-(ImageType &rightSide)
{
	int rows, cols, grayMax;
	rightSide.getImageInfo(rows, cols, grayMax);
	ImageType difference(rows, cols, grayMax);
	int dif1 = 0, dif2 = 0, dif3 = 0;
	for(int difRow = 0; difRow < rows; difRow++)
	{
		for(int difCol = 0; difCol < cols; difCol++)
		{
			dif1 = rightSide.getPixelVal(difRow, difCol);
			dif2 = getPixelVal(difRow, difCol);
			dif3 = int(dif1/2) - int(dif2/2);
			difference.setPixelVal(difRow, difCol, dif3);
		}
	}
	*this = difference;
	return *this;
}
//**********************************************
//Function name:negateImage
//Return Type:	void
//Purpose:	Changes the image passed to its
//			negative by subtracting the pixel
//			value from the max pixel value.
//*********************************************
void ImageType::negateImage(ImageType &positive)
{
	int N, M, Q, negPix;
	positive.getImageInfo(N, M, Q);
	ImageType negative(N, M, Q);
	for(int rows = 0; rows < N; rows++)
	{
		for(int cols = 0; cols < M; cols++)
		{
			negPix = positive.getPixelVal(rows, cols);
			negPix = (Q - negPix);
			negative.setPixelVal(rows, cols, negPix);
		}
	}
	positive = negative;
}
//**********************************************
//Function name:reflectImage
//Return Type:	void
//Purpose:	Reflects the image across an axis
//			determined by the flag argument 
//			passed to the function. If flag
//			is true, the image if reflected
//			across a horizontal line, if 
//			false, it reflects across a vertical
//			line.
//*********************************************
void ImageType::reflectImage(bool flag, ImageType &reflect)
{
	int rows, cols, maxGray, refVal;
	reflect.getImageInfo(rows, cols, maxGray);
	ImageType tempReflect(rows, cols, maxGray);
	//**********************************************
	//If statement checks the flag to see which
	//choice the user made. If true, the function 
	//executes the first statement. Otherwise, it
	//executes the second statement.
	//**********************************************
	if(flag)
	{
		for(int refRow = 0; refRow < rows; refRow++)
		{
			for(int refCol = 0; refCol < cols; refCol++)
			{
				refVal = reflect.getPixelVal(refRow, (cols - refCol - 1));
				tempReflect.setPixelVal(refRow, refCol, refVal);
			}
		}
	}
	else
	{
		for(int refRow = 0; refRow < rows; refRow++)
		{
			for(int refCol = 0; refCol < cols; refCol++)
			{
				refVal = reflect.getPixelVal((rows - refRow - 1), refCol);
				tempReflect.setPixelVal(refRow, refCol, refVal);
			}
		}
	
	}
	reflect = tempReflect;
}	
//**********************************************
//Function name:menu
//Return Type:	void
//Purpose:	Translates the image a number of 
//			pixels right. The distance is equal
//			to the distance variable passed to
//			the function.
//*********************************************	
void ImageType::translateImage(int distance, ImageType &tImage)
{
	int tRows, tCols, tGray, tVal;
	tImage.getImageInfo(tRows, tCols, tGray);
	ImageType translate(tRows, tCols, tGray);
	for(int tranRows = 0; tranRows < tRows; tranRows++)
	{
		for(int tranCols = 0; tranCols < tCols; tranCols++)
		{
			if((tranCols + distance) > tCols)
				tranCols = tCols;
			else
			{
				tVal = tImage.getPixelVal(tranRows, tranCols);
				translate.setPixelVal(tranRows, (tranCols + distance), tVal);
			}
		}
	}
	tImage = translate;			
}

void ImageType::rotateImage(int rotAngle, ImageType &rotImage)
{
	double radAngle;
	float pi = 3.1415927;
	float calcXSin, calcXCos, calcYSin, calcYCos;
	int imgCentX, imgCentY;
	imgCentX = N / 2;
	imgCentY = M / 2;
	radAngle = ((rotAngle * pi) / 180);
	for(int xCount = 0; xCount < N; xCount++)
	{
		for(int yCount = 0; yCount < M; yCount++)
		{
			calcXSin = imgCentX * sin(radAngle);
			calcXCos = imgCentX * cos(radAngle);
			calcYSin = imgCentY * sin(radAngle);
			calcYCos = imgCentY * cos(radAngle);
	
//**********************************************
//Function name:menu
//Return Type:	void
//Purpose:	Presents a menu for the user to 
//			choose what to do to the images
//			passed to the function. If an
//			invalid input is selected, 
//			the menu states that the input is 
//			invalid and then prints the menu
//			to the screen. Options 7 and 6 
//			do not work at this moment, but
//			the menu has been set up to handle
//			those selections.
//*********************************************		
void menu(ImageType &menuImage1, ImageType &menuImage2)
{
	int choice, LRc, LRr, ULc, ULr, N, M, Q, mean, refChoice, distance, angle;
	int scale;
	bool refFlag;
	ImageType tempRetImg;
	while(choice != 11)
	{
		cout << "Main menu:" << endl;
		cout << "1. Get subimage" << endl;
		cout << "2. Average gray value of the image" << endl;
		cout << "3. Enlarge the image" << endl;
		cout << "4. Shrink the image" << endl;
		cout << "5. Reflect the image" << endl;
		cout << "6. Translate the image" << endl;
		cout << "7. Rotate the image" << endl;
		cout << "8. Find the sum of 2 images" << endl;
		cout << "9. Find the difference of two images" << endl;
		cout << "10. Get the negative of the image" << endl;
		cout << "11. Exit" << endl;
		cout << "Enter your choice: ";
		cin >> choice;
		//**********************************************************
		//Switch statement handels all the function calls for the 
		//selection the user made.
		//*********************************************************
		switch(choice)
		{
			//*****************************************************
			//Each case handels the necessary user input for the 
			//function chosen. For example, case 1 takes 4 inputs
			//from the user and calls the function with those 4
			//inputs passed.
			//*****************************************************
			case 1:
			{
				menuImage1.getImageInfo(N, M, Q);
				cout << "There are " << N << " rows and " << M << " columns" << endl;
				cout << "Enter the coordinates for the upper left" << endl;
				cout << "And lower right corners for the subimage" << endl;
				cout << "Enter the row coordinates first" << endl;
				cout << "Upper Left: ";
				cin >> ULr;
				cout << endl;
				cout << "Lower Right: ";
				cin >> LRr;
				cout << endl;
				cout << "Columns now: " << endl;
				cout << "Upper Left: ";
				cin >> ULc;
				cout << endl;
				cout << "Lower Right: ";
				cin >> LRc;
				cout << endl;
				cout << "Fetching sub image..." << endl;
				menuImage1.getSubImage(ULr, ULc, LRr, LRc, menuImage1);
				menuImage1.getImageInfo(N, M, Q);
				cout << "The image now has " << N << " rows and " << M << " columns" << endl;
				cout << "Done!" << endl;
				break;
			}
			case 2:
			{
				mean = menuImage1.meanGray();
				cout << "The mean gray value is: " << mean << endl;
				break;
			}
			case 3:
			{
				cout << "Enter an integer to scale the image: ";
				cin >> scale;
				menuImage1.enlargeImage(scale, menuImage1);
				cout << "Image enlarged by a factor of " << scale << endl;
				break;
			}
			case 4:
			{
				cout << "Enter an integer to scale the image: ";
				cin >> scale;
				menuImage1.shrinkImage(scale, menuImage1);
				cout << "Image shrunk by a factor of " << scale << endl;
				break;
			}
			case 5:
			{
				refChoice = 0;
				while(refChoice == 0)
				{
					cout << "Reflect image submenu:" << endl;
					cout << "1. Reflect the image horizontally" << endl;
					cout << "2. Reflect the image vertically" << endl;
					cin >> refChoice;
					if(refChoice == 1)
					{
						refFlag = true;
					}
					else if(refChoice == 2)
					{
						refFlag = false;
					}
					else
					{
						cout << "Invalid argument" << endl;
					}
				}
				cout << "Reflecting image..." << endl;
				menuImage1.reflectImage(refFlag, menuImage1);
				cout << "Done!" << endl;						
				break;
			}
			case 6:
			{
				cout << "Enter the distance you would like to translate the image to the right: " << endl;
				cin >> distance;
				cout << "Translating the image " << distance << " pixels right..." << endl;
				menuImage1.translateImage(distance, menuImage1);
				cout << "Done!" << endl;
				break;
			}
			case 7:
			{
				cout << "Enter the angle (in degrees) of rotation: ";
				cin >> angle;
				cout << endl;
				menuImage1.rotateImage(angle, tempRetImg);
				cout << "Image rotated by " << angle << " degrees." << endl;
				break;
			}
			case 8:
			{
				cout << "Making a composite of the two images..." << endl;
				menuImage1 + menuImage2;
				cout << "Done!" << endl;
				break;
			}
			case 9:
			{
				cout << "Finding the difference of the two images..." << endl;
				menuImage1 - menuImage2;
				cout << "Done!" << endl;
				break;
			}
			case 10:
			{
				cout << "Negating Image..." << endl;
				menuImage1.negateImage(menuImage1);
				cout << "Done!" << endl;
				break;
			}
			case 11:
			{
				cout << "Exiting the program" << endl;
				break;
			}
			//**************************************************
			//Default case that takes any invalid input and 
			//resets the menu. 
			//**************************************************
			default:
			{
				cout << "Invalid choice entered. Please select a valid number between 1 and 12" << endl;
				break;
			}
		}
	}
}
					
	
			
